package core.neural;


import utility.*;
import core.*;
import core.genetic.*;
import core.sim.SingleSimulation;


/** Represents single neural network 
 * 
 * Example: 
 * if there are 2 hidden rows, with 3,4,3,2 
 * 
 * 
 * input(0) 	:	0	1	2	limit
 * 
 * 1st hidden(1):	0	1	2	3	limit
 * 
 * 2nd hidden(2):	0	1	2	limit
 * 
 * output(3) 	:	0	1	limit
 * 
 * ROWS = 3
 * NB_IN_ROW = { 3,4,3,2}
 * STARTS = { {0,4,8,12}, {16,19,22,25,28}, {}} 
 * 
 * 
 * */
public class IndividualBrain {		
	
	/** 0 - inputs, [1,ROWS-1] - hidden, ROWS - output*/
	private final int ROWS;
	private final int[] NB_IN_ROW;
	/** For ith unit in jth row, it has neighbours [starts[i][j],ends[i][j])  */
	private final int[][] STARTS;
	
	public final int LENGTH;
	
	
	public final int FEROMONS;
	public final int MEMORY;
	public final int SURROUNDINGS;
	public final int INFLUENCE;
	
	
	public final int INPUTS;
	public final int OUTPUTS;
	
	public final int[] HIDDEN;
	
	public IndividualBrain(SingleSimulation simulation, int feromons, int memory, int[] hidden) {
		FEROMONS = feromons;
		MEMORY = memory;
		SURROUNDINGS = simulation.getSurroungingsCount();
		INFLUENCE = simulation.getInfluencesCount();
		
		
		INPUTS = simulation.getInputCounts();
		OUTPUTS = simulation.getOutputCounts();
		
		HIDDEN = hidden;
			
		ROWS = HIDDEN.length+1;
		NB_IN_ROW = new int[ROWS+1];
		NB_IN_ROW[0] = INPUTS;
		NB_IN_ROW[ROWS] = OUTPUTS;
		for (int i = 0; i < HIDDEN.length; i++)
			NB_IN_ROW[i + 1] = HIDDEN[i];
		
		STARTS = new int[ROWS][];
		int br = 0;
		for(int i = 0;i<ROWS;i++) {
			STARTS[i] = new int[NB_IN_ROW[i]+1];
			for(int j = 0;j<=NB_IN_ROW[i];j++)
			{
				STARTS[i][j] = br;
				br+=NB_IN_ROW[i+1];
			}
		}
		
		LENGTH = br;
	}
	
	public double[] calculateOutputs(Individual ind, double[] inputs) {
		double[][] values = new double[ROWS+1][];
		values[0] = new double[NB_IN_ROW[0]+1];
		values[0][NB_IN_ROW[0]] = 1;
		for(int i = 0;i<NB_IN_ROW[0];i++)
			values[0][i] = inputs[i];
		for (int i = 1;i<ROWS;i++)
			values[i] = new double[NB_IN_ROW[i]+1];
		values[ROWS] = new double[NB_IN_ROW[ROWS]];
		
		for(int i = 0;i<ROWS;i++)
		{
			values[i][NB_IN_ROW[i]] = 1;
			for(int j = 0;j<=NB_IN_ROW[i];j++)
				for (int k = 0;k<NB_IN_ROW[i+1];k++)
					values[i+1][k] += values[i][j]*ind.getGene(STARTS[i][j]+k);
//			if (i+1<ROWS)
				for (int k = 0;k<NB_IN_ROW[i+1];k++)
					values[i+1][k] = Utils.sigmoid(values[i+1][k]);
		}
		
		return values[ROWS];
	}
	
	public void teach(Individual ind, double[] inputs, double[] desired, double koef) {
		double[][] values = new double[ROWS+1][];
		values[0] = new double[NB_IN_ROW[0]+1];
		values[0][NB_IN_ROW[0]] = 1;
		for(int i = 0;i<NB_IN_ROW[0];i++)
			values[0][i] = inputs[i];
		for (int i = 1;i<ROWS;i++)
			values[i] = new double[NB_IN_ROW[i]+1];
		values[ROWS] = new double[NB_IN_ROW[ROWS]];
		
		for(int i = 0;i<ROWS;i++)
		{
			values[i][NB_IN_ROW[i]] = 1;
			for(int j = 0;j<=NB_IN_ROW[i];j++)
				for (int k = 0;k<NB_IN_ROW[i+1];k++)
					values[i+1][k] += values[i][j]*ind.getGene(STARTS[i][j]+k);
//			if (i+1<ROWS)
				for (int k = 0;k<NB_IN_ROW[i+1];k++)
					values[i+1][k] = Utils.sigmoid(values[i+1][k]);
		}
		
		
		
		double[][] delta = new double[ROWS+1][];
		for (int i = 0;i<=ROWS;i++)
			delta[i] = new double[NB_IN_ROW[i]+1];
		

		
		for(int i = 0;i<NB_IN_ROW[ROWS];i++)
			delta[ROWS][i] = (desired[i]-values[ROWS][i]) * values[ROWS][i] * (1-values[ROWS][i]);
		
		for(int i = ROWS-1;i>=1;i--) {
			for(int j = 0;j<=NB_IN_ROW[i];j++)
			{
				delta[i][j] = 0;
				for (int k = 0;k<NB_IN_ROW[i+1];k++)
					delta[i][j] += delta[i+1][k] * ind.getGene(STARTS[i][j]+k);
				delta[i][j] *= values[i][j] * (1-values[i][j]);						
			}
		}
		
		
		for(int i = ROWS-1;i>=0;i--) 
			for(int j = 0;j<=NB_IN_ROW[i];j++)
				for (int k = 0;k<NB_IN_ROW[i+1];k++)
				{
					int g = STARTS[i][j]+k;
					ind.setGene(g, ind.getGene(g) + koef * delta[i+1][k] * values[i][j]);
				}		
	}	

	public void learn(Individual student, Individual mentor, double koef, int casesCount) {
		double[] in = new double[Mode.mode.brain.INPUTS];
		for(int i = 0;i<casesCount;i++) {
			for(int j = 0;j<in.length;j++)
				in[j] = 2*Utils.random.nextDouble()-1;
			
			double[] teach = Mode.mode.brain.calculateOutputs(mentor, in);

			Mode.mode.brain.teach(student, in, teach, koef);					
		}		
	}
	
	
	
}
